import { A, createAsync, query, redirect, useParams } from "@solidjs/router" import { Title } from "@solidjs/meta" import { createEffect, createSignal, For, Match, Show, Switch } from "solid-js" import { type Stripe, type PaymentMethod, loadStripe } from "@stripe/stripe-js" import { Elements, PaymentElement, useStripe, useElements, AddressElement } from "solid-stripe" import { PlanID, plans } from "../common" import { getActor, useAuthSession } from "~/context/auth" import { withActor } from "~/context/auth.withActor" import { Actor } from "@opencode-ai/console-core/actor.js" import { and, Database, eq, isNull } from "@opencode-ai/console-core/drizzle/index.js" import { WorkspaceTable } from "@opencode-ai/console-core/schema/workspace.sql.js" import { UserTable } from "@opencode-ai/console-core/schema/user.sql.js" import { createList } from "solid-list" import { Modal } from "~/component/modal" import { BillingTable } from "@opencode-ai/console-core/schema/billing.sql.js" import { Billing } from "@opencode-ai/console-core/billing.js" import { useI18n } from "~/context/i18n" import { useLanguage } from "~/context/language" import { formError } from "~/lib/form-error" import { Resource } from "@opencode-ai/console-resource" const getEnabled = query(async () => { "use server" return Resource.App.stage !== "production" }, "black.subscribe.enabled") const plansMap = Object.fromEntries(plans.map((p) => [p.id, p])) as Record const stripePromise = loadStripe(import.meta.env.VITE_STRIPE_PUBLISHABLE_KEY!) const getWorkspaces = query(async (plan: string) => { "use server" const actor = await getActor() if (actor.type === "public") throw redirect("/auth/authorize?continue=/black/subscribe/" + plan) return withActor(async () => { return Database.use((tx) => tx .select({ id: WorkspaceTable.id, name: WorkspaceTable.name, slug: WorkspaceTable.slug, billing: { customerID: BillingTable.customerID, paymentMethodID: BillingTable.paymentMethodID, paymentMethodType: BillingTable.paymentMethodType, paymentMethodLast4: BillingTable.paymentMethodLast4, subscriptionID: BillingTable.subscriptionID, timeSubscriptionBooked: BillingTable.timeSubscriptionBooked, }, }) .from(UserTable) .innerJoin(WorkspaceTable, eq(UserTable.workspaceID, WorkspaceTable.id)) .innerJoin(BillingTable, eq(WorkspaceTable.id, BillingTable.workspaceID)) .where( and( eq(UserTable.accountID, Actor.account()), isNull(WorkspaceTable.timeDeleted), isNull(UserTable.timeDeleted), ), ), ) }) }, "black.subscribe.workspaces") const createSetupIntent = async (input: { plan: string; workspaceID: string }) => { "use server" const { plan, workspaceID } = input if (!plan || !["20", "100", "200"].includes(plan)) return { error: formError.invalidPlan } if (!workspaceID) return { error: formError.workspaceRequired } return withActor(async () => { const session = await useAuthSession() const account = session.data.account?.[session.data.current ?? ""] const email = account?.email const customer = await Database.use((tx) => tx .select({ customerID: BillingTable.customerID, subscriptionID: BillingTable.subscriptionID, }) .from(BillingTable) .where(eq(BillingTable.workspaceID, workspaceID)) .then((rows) => rows[0]), ) if (customer?.subscriptionID) { return { error: formError.alreadySubscribed } } let customerID = customer?.customerID if (!customerID) { const customer = await Billing.stripe().customers.create({ email, metadata: { workspaceID, }, }) customerID = customer.id await Database.use((tx) => tx .update(BillingTable) .set({ customerID, }) .where(eq(BillingTable.workspaceID, workspaceID)), ) } const intent = await Billing.stripe().setupIntents.create({ customer: customerID, payment_method_types: ["card"], metadata: { workspaceID, }, }) return { clientSecret: intent.client_secret ?? undefined } }, workspaceID) } const bookSubscription = async (input: { workspaceID: string plan: PlanID paymentMethodID: string paymentMethodType: string paymentMethodLast4?: string }) => { "use server" return withActor( () => Database.use((tx) => tx .update(BillingTable) .set({ paymentMethodID: input.paymentMethodID, paymentMethodType: input.paymentMethodType, paymentMethodLast4: input.paymentMethodLast4, subscriptionPlan: input.plan, timeSubscriptionBooked: new Date(), }) .where(eq(BillingTable.workspaceID, input.workspaceID)), ), input.workspaceID, ) } interface SuccessData { plan: string paymentMethodType: string paymentMethodLast4?: string } function Failure(props: { message: string }) { const i18n = useI18n() return (

{i18n.t("black.subscribe.failurePrefix")} {props.message}

) } function Success(props: SuccessData) { const i18n = useI18n() return (

{i18n.t("black.subscribe.success.title")}

{i18n.t("black.subscribe.success.subscriptionPlan")}
{i18n.t("black.subscribe.success.planName", { plan: props.plan })}
{i18n.t("black.subscribe.success.amount")}
{i18n.t("black.subscribe.success.amountValue", { plan: props.plan })}
{i18n.t("black.subscribe.success.paymentMethod")}
{props.paymentMethodType}}> {props.paymentMethodType} - {props.paymentMethodLast4}
{i18n.t("black.subscribe.success.dateJoined")}
{new Date().toLocaleDateString(undefined, { month: "short", day: "numeric", year: "numeric" })}

{i18n.t("black.subscribe.success.chargeNotice")}

) } function IntentForm(props: { plan: PlanID; workspaceID: string; onSuccess: (data: SuccessData) => void }) { const i18n = useI18n() const stripe = useStripe() const elements = useElements() const [error, setError] = createSignal(undefined) const [loading, setLoading] = createSignal(false) const handleSubmit = async (e: Event) => { e.preventDefault() if (!stripe() || !elements()) return setLoading(true) setError(undefined) const result = await elements()!.submit() if (result.error) { setError(result.error.message ?? i18n.t("black.subscribe.error.generic")) setLoading(false) return } const { error: confirmError, setupIntent } = await stripe()!.confirmSetup({ elements: elements()!, confirmParams: { expand: ["payment_method"], payment_method_data: { allow_redisplay: "always", }, }, redirect: "if_required", }) if (confirmError) { setError(confirmError.message ?? i18n.t("black.subscribe.error.generic")) setLoading(false) return } if (setupIntent?.status === "succeeded") { const pm = setupIntent.payment_method as PaymentMethod await bookSubscription({ workspaceID: props.workspaceID, plan: props.plan, paymentMethodID: pm.id, paymentMethodType: pm.type, paymentMethodLast4: pm.card?.last4, }) props.onSuccess({ plan: props.plan, paymentMethodType: pm.type, paymentMethodLast4: pm.card?.last4, }) } setLoading(false) } return (

{error()}

{i18n.t("black.subscribe.form.chargeNotice")}

) } export default function BlackSubscribe() { const params = useParams() const i18n = useI18n() const language = useLanguage() const enabled = createAsync(() => getEnabled()) const planData = plansMap[(params.plan as PlanID) ?? "20"] ?? plansMap["20"] const plan = planData.id const workspaces = createAsync(() => getWorkspaces(plan)) const [selectedWorkspace, setSelectedWorkspace] = createSignal(undefined) const [success, setSuccess] = createSignal(undefined) const [failure, setFailure] = createSignal(undefined) const [clientSecret, setClientSecret] = createSignal(undefined) const [stripe, setStripe] = createSignal(undefined) const formatError = (error: string) => { if (error === formError.invalidPlan) return i18n.t("black.subscribe.error.invalidPlan") if (error === formError.workspaceRequired) return i18n.t("black.subscribe.error.workspaceRequired") if (error === formError.alreadySubscribed) return i18n.t("black.subscribe.error.alreadySubscribed") if (error === "Invalid plan") return i18n.t("black.subscribe.error.invalidPlan") if (error === "Workspace ID is required") return i18n.t("black.subscribe.error.workspaceRequired") if (error === "This workspace already has a subscription") return i18n.t("black.subscribe.error.alreadySubscribed") return error } // Resolve stripe promise once createEffect(() => { void stripePromise.then((s) => { if (s) setStripe(s) }) }) // Auto-select if only one workspace createEffect(() => { const ws = workspaces() if (ws?.length === 1 && !selectedWorkspace()) { setSelectedWorkspace(ws[0].id) } }) // Fetch setup intent when workspace is selected (unless workspace already has payment method) createEffect(async () => { const id = selectedWorkspace() if (!id) return const ws = workspaces()?.find((w) => w.id === id) if (ws?.billing?.subscriptionID) { setFailure(i18n.t("black.subscribe.error.alreadySubscribed")) return } if (ws?.billing?.paymentMethodID) { if (!ws?.billing?.timeSubscriptionBooked) { await bookSubscription({ workspaceID: id, plan: planData.id, paymentMethodID: ws.billing.paymentMethodID!, paymentMethodType: ws.billing.paymentMethodType!, paymentMethodLast4: ws.billing.paymentMethodLast4 ?? undefined, }) } setSuccess({ plan: planData.id, paymentMethodType: ws.billing.paymentMethodType!, paymentMethodLast4: ws.billing.paymentMethodLast4 ?? undefined, }) return } const result = await createSetupIntent({ plan, workspaceID: id }) if (result.error) { setFailure(formatError(result.error)) } else if ("clientSecret" in result) { setClientSecret(result.clientSecret) } }) // Keyboard navigation for workspace picker const { active, setActive, onKeyDown } = createList({ items: () => workspaces()?.map((w) => w.id) ?? [], initialActive: null, }) const handleSelectWorkspace = (id: string) => { setSelectedWorkspace(id) } let listRef: HTMLUListElement | undefined // Show workspace picker if multiple workspaces and none selected const showWorkspacePicker = () => { const ws = workspaces() return ws && ws.length > 1 && !selectedWorkspace() } return ( {i18n.t("black.subscribe.title")}
{(data) => } {(data) => } <>

{i18n.t("black.subscribe.title")}

${planData.id}{" "} {i18n.t("black.price.perMonth")} {(multiplier) => {i18n.t(multiplier())}}

{i18n.t("black.subscribe.paymentMethod")}

{selectedWorkspace() ? i18n.t("black.subscribe.loadingPaymentForm") : i18n.t("black.subscribe.selectWorkspaceToContinue")}

} >
{/* Workspace picker modal */} {}} title={i18n.t("black.workspace.selectPlan")}>
    { if (e.key === "Enter" && active()) { handleSelectWorkspace(active()!) } else { onKeyDown(e) } }} > {(workspace) => (
  • setActive(workspace.id)} onClick={() => handleSelectWorkspace(workspace.id)} > [*] {workspace.name || workspace.slug}
  • )}

{i18n.t("black.finePrint.beforeTerms")} ยท{" "} {i18n.t("black.finePrint.terms")}

) }